package com.shanhai.log.common.aspect;

import com.shanhai.log.common.entity.MethodData;
import com.shanhai.log.common.entity.RequestData;
import com.shanhai.log.common.entity.ResponseData;
import com.shanhai.log.common.entity.ShanHaiInterfaceLog;
import com.shanhai.log.common.enumeration.StateType;
import com.shanhai.log.service.ShanhaiLogManagerService;
import com.shanhai.log.util.HttpServletRequestUtil;
import com.shanhai.log.util.IpUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * 操作注解日志切面定义
 */
@Aspect
@Component
public class OperatorLogAspect {

    @Resource
    private ShanhaiLogManagerService shanhaiLogManagerService;

    //log4j日志工具
    private static Logger logger = LogManager.getLogger(OperatorLogAspect.class);

    //日志记录参数
    ShanHaiInterfaceLog shanHaiInterfaceLog = new ShanHaiInterfaceLog();

    @Value("${spring.application.name:}")
    private String appName;

    /**
     * 注解切入点
     */
    @Pointcut("@annotation(com.shanhai.log.common.aspect.OperatorLog)")
    public void pointcut() {
        logger.info("==========获取切点==========");
    }

    /**
     * 前置通知
     *
     * @param point
     */
    @Before("pointcut()")
    public void before(JoinPoint point) {
        logger.info("==========前置通知==========");
        //设置方法开始运行时间
        shanHaiInterfaceLog.setStartTime(new Date());
    }

    /**
     * 后置通知
     *
     * @param joinPoint
     */
    @After(value = "pointcut()")
    public void after(JoinPoint joinPoint) {
        logger.info("==========后置通知==========");
        //设置方法结束运行时间
        shanHaiInterfaceLog.setEndTime(new Date());
    }

    /**
     * 返回通知
     *
     * @param joinPoint
     * @param result
     */
    @AfterReturning(value = "pointcut()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result) {
        logger.info("==========返回通知==========");
        shanHaiInterfaceLog.setState(StateType.SUCCESS.getValue);
        shanHaiInterfaceLog.getResponseData().setResult(result);
    }

    /**
     * 异常通知
     *
     * @param joinPoint
     * @param e
     */
    @AfterThrowing(pointcut = "pointcut()", throwing = "e")
    public void afterThrowing(JoinPoint joinPoint, Exception e) {
        logger.info("==========异常通知==========");
        logger.info("==========异常类型:" + e.toString());
        logger.info("==========异常信息:" + e.getMessage().toString());

        String exceptionOutLog = "";
        StackTraceElement[] trace = e.getStackTrace();
        for (StackTraceElement s : trace) {
            exceptionOutLog += "\r\n" + s;
        }
        logger.info("==========异常堆栈:" + exceptionOutLog);
        shanHaiInterfaceLog.setState(StateType.FAIL.getValue);
    }

    /**
     * 环绕通知
     *
     * @param joinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "pointcut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("==========环绕通知-开始==========");
        Object result = null;
        try {
            //获取方法注解对象
            MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
            Method method = methodSignature.getMethod();

            //操作方法类型    请求类型    响应类型
            addShanHaiInterfaceLog(joinPoint, method, shanHaiInterfaceLog);
            addMethodData(joinPoint, method, shanHaiInterfaceLog);
            addRequestData(joinPoint, method, shanHaiInterfaceLog);

            result = joinPoint.proceed();
        } catch (Exception e) {
            ResponseData responseData = shanHaiInterfaceLog.getResponseData();
            responseData.setExceptionType(e.toString());
            responseData.setExceptionMessage(e.getMessage());
            responseData.setExceptionSrintStackTrace(Arrays.toString(e.getStackTrace()));
        } finally {
            countTakeUpTime(shanHaiInterfaceLog);
            //ShanHai日志管理器实现
            shanhaiLogManagerService.defaultSaveShanhaiLog(shanHaiInterfaceLog);
            shanHaiInterfaceLog = null;
        }
        logger.info("==========环绕通知-结束==========");
        return result;
    }

    /**
     * 添加日志信息
     *
     * @param method
     * @param interfaceLog
     * @return
     */
    public void addShanHaiInterfaceLog(JoinPoint joinPoint, Method method, ShanHaiInterfaceLog interfaceLog) {
        OperatorLog operatorLog = method.getAnnotation(OperatorLog.class);
        interfaceLog.setOperationName(operatorLog.operationName());
        interfaceLog.setOperationType(String.valueOf(operatorLog.operationType()));
    }

    /**
     * 添加日志类方法信息
     *
     * @param joinPoint
     * @param interfaceLog
     * @return
     */
    public void addMethodData(JoinPoint joinPoint, Method method, ShanHaiInterfaceLog interfaceLog) {
        MethodData methodData = interfaceLog.getMethodData();
        methodData.setAppName(appName == null ? null : appName);
        methodData.setClassPath(joinPoint.getTarget().getClass().getName());
        methodData.setClassName(joinPoint.getTarget().getClass().getSimpleName());
        methodData.setMethodName(joinPoint.getSignature().getName());
        methodData.setParameterTypes(Arrays.toString(((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes()));
    }

    /**
     * 添加日志请求信息
     *
     * @param method
     * @param interfaceLog
     * @return
     */
    public void addRequestData(JoinPoint joinPoint, Method method, ShanHaiInterfaceLog interfaceLog) {
        RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
        RequestData requestData = interfaceLog.getRequestData();
        requestData.setRequestType(Arrays.toString(requestMapping.method()));
        requestData.setIpAddr(IpUtil.getIpAddr(HttpServletRequestUtil.getHttpServletRequest()));
        requestData.setUrlPath(Arrays.toString(requestMapping.value()));
        requestData.setArguments(Arrays.toString(joinPoint.getArgs()));
    }

    /**
     * 计算运行耗时
     *
     * @param interfaceLog
     */
    public void countTakeUpTime(ShanHaiInterfaceLog interfaceLog) {
        //计算运行耗时
        long startTime = interfaceLog.getStartTime().getTime();
        long endTime = interfaceLog.getEndTime().getTime();
        interfaceLog.setTakeUpTime(endTime - startTime);
    }

}